home *** CD-ROM | disk | FTP | other *** search
- #!/usr/local/bin/gawk -f
- #!/usr/bin/awk -f
- # @(#) mmdfq.gawk 2.4 97/05/27
- # 92/09/21 john h. dubois iii (john@armory.com)
- # 92/09/28 Converted to #!gawk script. Added options and command line args.
- # 93/12/27 Added conmtaAdDrRiksMS options, assorted bugfixes
- # 94/05/10 Make stat exit 0 for gawk
- # 95/03/16 Added ( and ) to the ^ and $ used to anchor patterns, so that e.g.
- # addr1|addr2 will work as expected.
- # 96/01/20 Make tempfile name begin with #
- # 96/02/09 Added x option and checking for correct contents of address files.
- # 96/06/03 Don't try to get message file sizes if there are none.
- # Complain about empty address files explicitly.
- # 96/10/14 Use termtype for default width. Renamed some header fields so that
- # they match option letters that select by that field.
- # Cleaned up hostname canonicalization. Added bpq options.
- # 96/12/06 Print control chars in msg file in symbolic form in error message.
- # 97/02/09 Replaced ADR options with leading ! in value for adr options.
- # 97/05/27 Changed NO_ADDR to !ADDR so output lines up in mmdfsel.
- # Print - for flags if no address file. Let E & M be given together.
- # Added O option. Made -t work correctly.
- # Made -p only print paths to whichever of addr/msg files exist.
-
- # Uses gawk for strftime() and systime()
-
- BEGIN {
- Name = "mmdfq"
- Usage = \
- "Usage:\n"\
- Name " [-AehiklnoOpqsVME] [-w<width>] [-c<chan>[,<chan]>] [-t<time>]\n"\
- " [-a<address>] [-d<host>] [-r<return-address>] [-b<bounce-message>]\n"\
- " [-x<level>] [message-ID ...]"
- ARGC = Opts(Name,Usage,"Ab;c;VMS;lmnoOpqhiksw<t;a;d;r;eEx>",0)
- if (Options["h"]) {
- printf \
- "%s: list messages spooled in the MMDF mail queues.\n"\
- "%s\n"\
- "General options:\n"\
- "-h: Print this help.\n"\
- "-x<level>: Turn on debugging at level <level>, which should be a positive\n"\
- " integer.\n"\
- "Formatting options:\n"\
- "-l: Print fields in longer (more detailed) format. The full Message-ID is\n"\
- " printed rather than just the variable part; source routes and the\n"\
- " local domain are not removed from return and recipient addresses, and\n"\
- " the destination host (if any) is prefixed to recipient addresses in !\n"\
- " format.\n"\
- "-e: Print only message-IDs.\n"\
- "-p: Print only the full paths to the address and message files. Only the\n"\
- " path to the address file in the \"addr\" directory is printed. Each\n"\
- " pair of paths is separated from the next by a blank line.\n"\
- "-n: Do not print headers.\n"\
- "-o: Print in a format that has only one line per message,\n"\
- " even if the message is in more than one queue.\n"\
- "-V: Verbose operation. Print the size, time of queuing, and flags in\n"\
- " addition to other fields. The flags printed have these meanings:\n"\
- " C (Citation): Warning/failure messages will include only a citation.\n"\
- " R (Return): Message should be returned if it cannot be delivered.\n"\
- " W (Warn): Warning messages should be sent if message is not delivered.\n"\
- " L (Late): A warning message has been sent regarding this message.\n"\
- "-O: The age of each message (how long it has been queued) is printed\n"\
- " instead of the date & time it was queued. Used with -V.\n"\
- "-w<width>: Truncate displayed lines to at most <width> characters.\n"\
- " The header is not truncated. The default for <width> is one less than\n"\
- " than the number of columns the terminal has. If -w0 is given, lines\n"\
- " are not truncated.\n"\
- "-A: Print only the contents of address files. Each is preceded by its\n"\
- " message ID and separated from the next by a blank line.\n"\
- "-q: Quiet operation; do not print any report. Errors are still printed,\n"\
- " and any message processing actions are still taken.\n"\
- "Message selection options:\n"\
- "-a<address>, -d<host>, -r<return-address>: For each message, each pending\n"\
- " recipient address at the destination is matched against <address>,\n"\
- " each pending destination host (if any) is matched against <host>, and\n"\
- " the return address is matched against <return-address>, respectively.\n"\
- " A match causes the message to be selected. If a value that begins\n"\
- " with a '!' is given, any message that matches the value is excluded\n"\
- " (all others are selected). If more than one option is given, all\n"\
- " selecting options must result in selections and all excluding options\n"\
- " must not result in exclusions in order for the message to be selected.\n"\
- " The string to match against is a pattern in the style of egrep,\n"\
- " implicitely anchored at the beginning and end. Note that recipient\n"\
- " addresses in queues other than \"local\" typically include a host\n"\
- " part, and this must be included in the pattern. The 'l' option\n"\
- " affects the a and r options.\n"\
- "-c<channel[,channel]>: Select messages queued in the given channels.\n"\
- "-t<time>: Select messages queued <time> ago. <time> is given as\n"\
- " days[:hh[mm[ss]]]. A message will be selected if it was queued at\n"\
- " least the given amount of time ago and no more than the given amount of\n"\
- " time plus the time specification precision ago. Examples:\n"\
- " -t:01 selects messages at least 1 hour old but less than 2 hours old.\n"\
- " -t:0130 selects messages 1.5 hours through 1.5 hours + 59 seconds old.\n"\
- " If -<time> or +<time> is given, messages created less than or more than\n"\
- " the given amount of time ago are selected. Messages with no address\n"\
- " file are also selected.\n"\
- "-M, -E: Select only messages that have no message (-M) or address (-E)\n"\
- " file. -M and -E may be given together.\n"\
- "Message processing options (act upon the selected messages):\n"\
- "-i: Attempt immediate delivery of messages.\n"\
- "-k: Kill messages. Check which messages are selected before using this.\n"\
- "-s: Resend messages to all addresses not yet successfully delivered to,\n"\
- " and remove (kill) the original messages. The original return address\n"\
- " is used. This is used for acting on modified MMDF setup, in which\n"\
- " messages may need to be put in a different queue.\n"\
- "-S<pattern;string>: Like -s, except that for each recipient address, a\n"\
- " single substitution is done, replacing the largest match against\n"\
- " pattern with string. Example: mail to certain users@armory.com is\n"\
- " forwarded to user@aldebaran.armory.com. However, aldebaran.armory.com\n"\
- " is down and mail for those users is temporarily forwarded to a\n"\
- " different host. To deal with any mail that built up before the change\n"\
- " was made: -S'@aldebaran.armory.com$;@armory.com'\n"\
- "-b<bounce-message>: Send selected messages back to the return address and\n"\
- " dequeue them. In the returned message, the message contents are\n"\
- " prefixed with a report that they are undeliverable, with the reason\n"\
- " given being <bounce-message>.\n",
- Name,Usage
- exit 0
- }
-
- if ("x" in Options) {
- Debug = Options["x"]
- printf "Debugging is on at level %d\n",Debug > "/dev/stderr"
- }
- MMDFHome = "/usr/spool/mmdf/lock/home"
- GoToHome = "cd " MMDFHome ";"
-
- # Convince awk that these are arrays
- split("",Msg," ")
- split("",Addr," ")
- split("",Q," ")
- if ("S" in Options && Options["S"] !~ ";") {
- print "Bad recipient address substitution: no semicolon." \
- > "/dev/stderr"
- exit 1
- }
-
- LimChan = "c" in Options
- if (ARGC <= 1) {
- # Put a : at the end so that if the last test fails gawk won't print
- # a warning message due a nonzero exit value
- if (LimChan) {
- split(Options["c"],Chans,",")
- for (i in Chans)
- queueFiles = queueFiles " q." Chans[i] "/*"
- }
- else
- queueFiles = " q.*/*"
- Cmd = GoToHome \
- "for i in addr/* msg/* " queueFiles "; do [ -f \"$i\" ] && echo $i; done; :"
- # Set Msg[], Addr[], and Q[] according to file names in the spool dirs
- if (Debug)
- printf "Getting all messages with: %s\n",Cmd > "/dev/stderr"
- while ((Cmd | getline) == 1) {
- split($0,Comp,"/")
- RecordFilename(Comp[1],Comp[2],Msg,Addr,Q)
- }
- close(Cmd)
- }
- else {
- MessageIDsGiven = 1
- # Set QDirs[] to the dirs that address and data files are spooled in
- FindQueues(QDirs)
- QDirs["addr"]
- QDirs["msg"]
- NumMsgs = ARGC - 1
-
- for (i = 1; i < ARGC; i++)
- if (i in ARGV) {
- MessageID = ARGV[i]
- # Message IDs can be given with or without the leading "msg."
- if (MessageID !~ "^msg.")
- MessageID = "msg." MessageID
- for (Dir in QDirs) {
- File = MMDFHome "/" Dir "/" MessageID
- if ((getline < File) >= 0)
- RecordFilename(Dir,MessageID,Msg,Addr,Q)
- close(File)
- }
- if (!(MessageID in Q || MessageID in Msg||MessageID in Addr)) {
- ErrMsg(MessageID ": No such message.")
- NumMsgs--
- }
- }
- if (!NumMsgs)
- exit
- }
- if ("w" in Options)
- HeadTailInit(-1,Options["w"] ? Options["w"] : -1,0,0)
- else
- HeadTailInit(-1)
- MessageSizes(Msg)
- exit PrintJobs(Msg,Addr,Q,"V" in Options,"l" in Options,"o" in Options,
- LimChan, !("n" in Options || "e" in Options || "p" in Options || \
- "q" in Options || "A" in Options),"e" in Options,"p" in Options,
- "q" in Options,"A" in Options,Options["a"],Options["d"],Options["r"],
- "M" in Options,"E" in Options,Options["t"],"i" in Options,"k" in Options,
- "s" in Options || "S" in Options,Options["S"],Options["b"],MessageIDsGiven,
- "O" in Options)
- }
-
- # Makes the indices of Queues the set of queue directory names
- # Global variables: GoToHome should be a shell command to cd to
- # the MMDF spool home directory
- function FindQueues(Queues, Cmd,QueueList) {
- Cmd = GoToHome \
- "for file in q.*; do [ -d $file ] && l=\"$l $file\"; done; echo $l"
- if (Debug)
- printf "Getting queue directory names with: %s\n",Cmd > "/dev/stderr"
- Cmd | getline QueueList
- close(Cmd)
- if (Debug)
- printf "Got queues: %s\n",QueueList > "/dev/stderr"
- MakeSet(Queues,QueueList," ")
- delete Queues[""]
- }
-
- # Makes File an index of Msg[], Addr[], or Q[],
- # depending on whether Dir is "msg", "addr", or begins with "q.".
- # If Dir begins with "q.", Dir without the leading "q." is made the value
- # of or concatenated to the value of Q[File].
- function RecordFilename(Dir,File,Msg,Addr,Q) {
- if (Dir == "addr") {
- Addr[File]
- if (Debug > 5)
- printf "Adding %s to Addr[]\n",File > "/dev/stderr"
- }
- else if (Dir == "msg") {
- Msg[File]
- if (Debug > 5)
- printf "Adding %s to Msg[]\n",File > "/dev/stderr"
- }
- else
- if (Dir !~ /^q\../)
- ErrMsg(Dir ": invalid directory name.")
- else
- if (File in Q) {
- Q[File] = Q[File] " " Dir
- if (Debug)
- printf "Adding %s to Q[%s]\n",Dir,File > "/dev/stderr"
- }
- else {
- Q[File] = Dir
- if (Debug)
- printf "Setting Q[%s] to %s\n",File,Dir > "/dev/stderr"
- }
- }
-
- # For each message-ID in Msg, sets Msg[message-ID] to the size of
- # the message file.
- # Global vars: MMDFHome should be the MMDF spool home directory
- function MessageSizes(Msg, File,Cmd,TmpFile) {
- if (IsEmpty(Msg))
- return
- # Do this first so it can all be done by one stat
- for (File in Msg)
- FileNames = FileNames " " File
- FileNames = substr(FileNames,2)
- # If more names than will fit on cmd line, put them in a file for stat
- if (length(FileNames) > 4000) {
- getline TmpFile < "/dev/pid"
- close("/dev/pid")
- TmpFile = "/tmp/#mmdfq." TmpFile
- gsub(" ","\n",FileNames)
- print FileNames > TmpFile
- close(TmpFile)
- Cmd = "cd " MMDFHome "/msg; stat -nr -fns -c' ' < " TmpFile \
- "; exec rm -f " TmpFile
- }
- else {
- # Cut off msg. and add later since this all has to fit on a
- # command line.
- gsub("msg.","",FileNames)
- # exit 0 for gawk
- Cmd = "cd " MMDFHome "/msg; for file in " FileNames \
- "; do echo msg.$file; done | stat -nr -fns -c' '; exit 0"
- }
- if (Debug > 4)
- printf "Getting message file sizes with: %s\n",Cmd > "/dev/stderr"
- while ((Cmd | getline) == 1) {
- Msg[$1] = $2
- if (Debug > 5)
- printf "Size of %s is %s\n",$1,$2 > "/dev/stderr"
- }
- close(Cmd)
- }
-
- # Reads address file AddrFile
- # Makes Queues[] the set of channels that the message has addresses
- # still pending delivery through.
- # Addrs[], indexed by channel name, is set to a comma-separated list of the
- # addresses awaiting delivery (those not yet successfully delivered to) by
- # each channel.
- # Info["date"] is set to the messages creation date in UNIX format.
- # Info["return_addr"] is set to the return address.
- # The index "late" is set if the message is late.
- # The indexes "c", "q", and "z" are set if the message has the flags
- # corresponding to those submit options set. See the description below.
-
- # Return value: 1 if a message should be selected, 0 if not, -1 on error
-
- # Format of an address file:
- # <creation-date><late><flags>
- # <return-address>
- # <temp-ok> <mode> <queue> <host> <local>
- # where
- # Line 1:
- # <creation-date> is the creation time of the message, as numeric epoch time.
- # It is used to sort the queue so mail is delivered in the order submitted.
- # <late> is '*' if warning message has been sent about this message, else 'm'
- # <flags> is a numeric representation of a 16-bit flag value. The submit
- # option that turns on each bit is given after the bit-place value.
- # 1: (c) warning/failure messages should include only citation. If not set,
- # the entire message is returned.
- # 2: (q) Do not return mail. If not set, mail is returned to sender if it
- # hasn't been completely processed within failtime.
- # 4: (z) Do net send late warnings. If not set, warnings should be sent to
- # the return-mail address if the entire address list has not been
- # processed within warntime.
- # Line 2:
- # <return-address> is the sender address.
- # Lines 3+:
- # <temp-ok> is '+' if address has been verified by receiving host, else '-'
- # <mode> tells where to send message: to mailbox(m), tty(t), both(b),
- # either(e), or processing completed(*)
- # <queue> is the name of the channel this address has been queued for.
- # <host> is the official domain name of hte receiving host.
- # <local> is the recipient's address on the receiving host.
- # Example:
- # 756648151m0
- # filbo@armory.com
- # - * local, , spcecdt
- # - m smtp "sco.com" "belal@sco.com"
- function ReadAddrs(AddrFile,Queues,Addrs,Info,Long,
- AddrPat,NotAddrPat,HostPat,NotHostPat,
- Recip,DestHost,Channel,OldFS,Ret,Line,GotPat,ASelect,HSelect,Exclude,Select,
- dateLine,Message,Flags) {
- split("",Info) # empty this so flags can be set
- Message = AddrFile
- sub(".*/","",Message)
- # Allow for commas in a field separator; make sure no tabs exist in fields
- OldFS = FS
- FS = "[ \t]*[ ,\t][ \t]*"
- Ret = 0
- # Get creation date line
- if ((ret = (getline dateLine < AddrFile)) != 1) {
- if (!ret)
- ErrMsg("Empty address file: " AddrFile)
- else if (ret == -1)
- ErrMsg("Could not open address file (" ERRNO "):\n" AddrFile)
- else
- ErrMsg("Could not open address file (unknown getline error " ret \
- "):\n" AddrFile)
- Ret = -1
- }
- # Current input line is creation-date/late/flags line, which should
- # look like this: 756648151m0
- else if (dateLine !~ /^[0-9]+[m*]0*[0-7]$/) {
- ErrMsg("Bad line 1 in address file for message " Message)
- Ret = -1
- }
- else if ((getline Info["return_addr"] < AddrFile) != 1) {
- ErrMsg("Address file for message " Message \
- " has no return-address line")
- Ret = -1
- }
- else {
- Flags = dateLine
- sub("[^0-9].*","",dateLine) # Get rid of late and flags info
- sub("^[0-9]+","",Flags) # Get rid of timestamp
- if (Debug)
- printf "Date: %s Flags: %s\n",dateLine,Flags > "/dev/stderr"
- if (Flags ~ /\*/)
- Info["late"]
- Flags = substr(Flags,2)+0
- if (Debug > 3)
- printf "intflags = %d\n",Flags > "/dev/stderr"
- if (Flags >= 4) {
- Info["z"]
- Flags -= 4
- if (Debug > 3)
- printf "bit 4, nowarn (z), is set\n" > "/dev/stderr"
- }
- if (Flags >= 2) {
- Info["q"]
- Flags -= 2
- if (Debug > 3)
- printf "bit 2, noreturn (q), is set\n" > "/dev/stderr"
- }
- if (Flags >= 1) {
- Info["c"]
- Flags -= 1
- if (Debug > 3)
- printf "bit 1, citeonly (c), is set\n" > "/dev/stderr"
- }
- Info["date"] = dateLine
- if (GotPat = HostPat != "" || NotHostPat != "" || AddrPat != "" || \
- NotAddrPat != "") {
- HSelect = HostPat == ""
- ASelect = AddrPat == ""
- }
- # Get recipient lines
- Line = 2
- while ((getline < AddrFile) == 1) {
- Line++
- # These lines have format: <temp-ok> <mode> <queue> <host> <local>
- if ($1 != "+" && $1 != "-") {
- ErrMsg("Bad field 1 (temp-ok field) on line " Line \
- " of address file for message " Message ".\n"\
- "Entire line is: " Uncontrol($0))
- Ret = -1
- break
- }
- if ($2 == "*") # Delivery completed
- continue
- gsub("\"","")
- Recip = $NF
- DestHost = $(NF - 1)
- if (!Long)
- Recip = CanonShort(Recip)
-
- if (GotPat) {
- HSelect = HSelect || DestHost ~ "^(" HostPat ")$"
- ASelect = ASelect || Recip ~ "^(" AddrPat ")$"
- Exclude = Exclude ||
- NotHostPat != "" && DestHost ~ "^(" NotHostPat ")$" ||
- NotAddrPat != "" && Recip ~ "^(" NotAddrPat ")$"
- }
-
- if (DestHost != "" && (Long || $NF !~ "@"))
- Recip = DestHost "!" Recip
- Channel = $3
- if (Channel in Queues)
- Addrs[Channel] = Addrs[Channel] "," Recip
- else {
- Addrs[Channel] = Recip
- Queues[Channel]
- }
- }
- }
- close(AddrFile)
- if (!Ret && Line < 3) {
- ErrMsg("No recipient lines in address file for message " Message)
- Ret = -1
- }
- FS = OldFS
- Select = (!GotPat || HSelect && ASelect && !Exclude)
- if (!Select && Debug) {
- printf "No match for recip addrs or hosts for message %s\n",
- Message > "/dev/stderr"
- }
- return Ret ? Ret : Select
- }
-
- # Convert a time spec of the form days[:hh[mm[ss]]] into a range of times,
- # in UNIX epoch form. The time given is relative to RelTime.
- # The low (old) end of the time range is put in Range["lo"];
- # the high (new) end of the time range is put in Range["hi"].
- # The range starts the given amount of time ago and extends into the past
- # for an amount of time equal to the time precision given (one day, hour,
- # minute, or second).
- # If -<time> or +<time> is given, the behaviour is the same except that
- # the hi end becomes 2^31 or the lo end becomes 0, respectively.
- # On success, null is returned; on error, and error message.
- function TimeSpec2Time(Time,RelTime,Range,
- RangeMod,Elem,TimeLen,Mult,i,nElem,f,m) {
- if (Time ~ /^[-+]/) {
- RangeMod = substr(Time,1,1)
- Time = substr(Time,2)
- }
- nElem = split(Time,Elem,":")
- TimeLen = length(Elem[2])
- if (nElem < 1 || nElem > 2 || Time !~ /^[0-9]*(:[0-9]+)?$/ || TimeLen > 6)
- return "Invalid time format"
- # Test for this separately so an explicit error message can be returned,
- # since it is likely to be a common mistake
- if (TimeLen % 2)
- return "Invalid time format: odd number of digits after ':'"
- RelTime -= Elem[1] * (Secs = 24*60*60)
- split("86400,3600,60,1",Mult,",")
- TimeLen /= 2 # how many units in time; will be 0..3
- Time = Elem[2]
- for (i = 1; i <= TimeLen; i++) {
- f = substr(Time,i*2-1,2)
- m = Mult[i+1]
- RelTime -= f * m
- if (Debug)
- printf "%d * %d seconds\n",f,m > "/dev/stderr"
- }
- if (RangeMod == "+")
- Range["lo"] = 0
- else
- Range["lo"] = RelTime - Mult[TimeLen+1] - 1
- if (RangeMod == "-")
- Range["hi"] = 2^31
- else
- Range["hi"] = RelTime
- return ""
- }
-
- # Pring mmdf job status.
- # MsgSizes[] contains an index for each message file found; the value is
- # the size of the message file.
- # Addr[] contains an index for each address file found in the addr directory.
- # Q[] contains an index for each address file found in any queue directory.
- # The value is a space-separated list of queue dirs the address file was in.
- # LimChan is true if specific queues are being processed.
- # The other args are output formatting & message selection options.
- function PrintJobs(MsgSizes,Addr,Q,Verbose,Long,OneLine,LimChan,Header,IDsOnly,
- PathsOnly,Quiet,AFOnly,AddrPat,HostPat,ReturnPat,OnlyNoMsg,OnlyNoAddr,Time,
- Deliver,Kill,Resubmit,Substitution,BounceMessage,MessageIDsGiven,printAge,
- PrintFile,Format,File,Queue,ReturnAddr,DestHost,QueueLine,AddrLine,Sep,
- NotAddrPat,NotHostPat,NotReturnPat,
- DestHosts,CreationDate,Range,Msg,RetAddrs,Recips,GoodAddrs,Recipient,CurTime) {
- if (AddrPat ~ /^!/) {
- NotAddrPat = substr(AddrPat,2)
- AddrPat = ""
- }
- if (HostPat ~ /^!/) {
- NotHostPat = substr(HostPat,2)
- HostPat = ""
- }
- if (ReturnPat ~ /^!/) {
- NotReturnPat = substr(ReturnPat,2)
- ReturnPat = ""
- }
- CurTime = systime() # Do this only once, for consistency
- # Convince awk that Files is an array.
- # Files is not in the function declaration because it fails under gawk.
- split("",Files," ")
- if (Time != "") {
- if (Msg = TimeSpec2Time(Time,CurTime,Range)) {
- ErrMsg(Msg)
- return 1
- }
- if (Debug)
- printf "Selecting messages queued %s .. %s\n",
- strftime("%c",Range["lo"]),strftime("%c",Range["hi"]) \
- > "/dev/stderr"
- }
- # Make Addr[] tell where an address file for every message that has one is.
- FindAddrFiles(Addr,Q,LimChan)
- # Ignore zero-length message files w/no address file left by checkaddr
- # Don't do this if specific channels are being processed, because Addr
- # will be emptied by FindAddrFiles(), so we would skip all zero-length
- # message files.
- if (!LimChan)
- for (File in MsgSizes)
- if (!MsgSizes[File] && !(File in Addr))
- delete MsgSizes[File]
- # Make Files contain all message-IDs found in any dir
- if (LimChan)
- CopySet(Addr,Files)
- else
- Union(Addr,MsgSizes,Files)
- if (IsEmpty(Files)) {
- ErrMsg("mmdfq: No messages spooled in MMDF queue.")
- return 0
- }
-
- Format = MakeFormat(Long,OneLine,Verbose,Header,printAge)
- # If doing a short display, need the local mail name so that it can be
- # removed from addresses.
- if (!Long && (Err = GetMailName()) != "")
- ErrMsg(Err)
-
- # For every message that has an address or message file...
- for (File in Files) {
- if (Debug)
- printf "Processing message %s\n",File > "/dev/stderr"
- # Emtpy Queues and Addrs
- split("",Queues," ")
- split("",Addrs," ")
- split("",DestHosts," ")
- if (File in Addr) {
- # If only listing no-addr files, this one is not of interest
- if (OnlyNoAddr && !OnlyNoMsg)
- continue
- GoodAddrs = ReadAddrs(MMDFHome "/" Addr[File] "/" File,Queues,
- Addrs,Info,Long,AddrPat,NotAddrPat,HostPat,NotHostPat)
- # If message IDs were given on the command line, don't quit
- # even if there is an error reading the address files, because
- # we may be intentionally killing messages with bad address files.
- if (!MessageIDsGiven && GoodAddrs != 1)
- continue
- RetAddrs[File] = ReturnAddr = Info["return_addr"]
- CreationDate = Info["date"]
- if (Time != "" && \
- !(Range["lo"] <= CreationDate && CreationDate <= Range["hi"])) {
- if (Debug)
- printf "Time not in range for %s\n",File > "/dev/stderr"
- continue
- }
- if (printAge)
- Date = sec2dhms2(CurTime-CreationDate)
- else
- Date = strftime("%y/%m/%d,%T",CreationDate)
- delete Addr[File]
- }
- else {
- if (Debug)
- printf "No address file for %s\n",File > "/dev/stderr"
- Queues["!ADDR"]
- Addrs["!ADDR"] = ReturnAddr = Date = "!ADDR"
- }
- if (Long)
- PrintFile = File
- else {
- ReturnAddr = CanonShort(ReturnAddr)
- PrintFile = substr(File,5) # Remove leading 'msg.' from message ID
- }
- if (ReturnPat != "" && (ReturnAddr !~ "^(" ReturnPat ")$") ||
- NotReturnPat != "" && (ReturnAddr ~ "^(" NotReturnPat ")$")) {
- if (Debug)
- printf "Deselection of message %s based on return address\n",
- File > "/dev/stderr"
- continue
- }
- AddrLine = QueueLine = ""
- for (Queue in Queues) {
- QueueLine = QueueLine "," Queue
- AddrLine = AddrLine "," Addrs[Queue]
- }
- AddrLine = substr(AddrLine,2)
- if (File in MsgSizes) {
- if (OnlyNoMsg && !OnlyNoAddr)
- continue
- }
- else
- MsgSizes[File] = "NO_MSG"
- if (OnlyNoMsg && OnlyNoAddr &&
- !(MsgSizes[File] == "NO_MSG" || "!ADDR" in Addrs))
- continue
- # Message selection completed
- if (IDsOnly)
- print File
- else if (PathsOnly) {
- printf Sep
- if (!("!ADDR" in Addrs))
- printf "%s/addr/%s\n",MMDFHome,File
- if (MsgSizes[File] != "NO_MSG")
- printf "%s/msg/%s\n",MMDFHome,File
- Sep = "\n"
- }
- else if (AFOnly) {
- printf Sep
- print File
- cat(MMDFHome "/addr/" File)
- Sep = "\n"
- }
- else if (!Quiet) {
- if (OneLine) {
- split("",Queues)
- Queues[substr(QueueLine,2)]
- Addrs[substr(QueueLine,2)] = AddrLine
- }
- for (Queue in Queues)
- PrintLine(Verbose,Format,Queue,PrintFile,MsgSizes[File],
- Date,ReturnAddr,Addrs[Queue],Info)
- }
- Recips[File] = AddrLine
- SelectedMsgs[File]
- }
- ProcMessages(Deliver,Kill,Resubmit,Substitution,SelectedMsgs,RetAddrs,
- Recips,BounceMessage)
- return 0
- }
-
- # Returns a string containing as many elements from set Items as will fit
- # in a string of length MaxLen with each pair of elements separated by Sep.
- # The items are removed from Items. Always returns at least one item,
- # unless there are no items in Items.
- function MakeMaxStr(Sep,Items,MaxLen, Len,SepLen,S,ILen) {
- for (I in Items) {
- S = I
- Len = length(S)
- delete Items[I]
- break
- }
- SepLen = length(Sep)
- for (I in Items) {
- if (Len + (ILen = length(I) + SepLen) <= MaxLen) {
- S = S Sep I
- Len += ILen
- delete Items[I]
- }
- else
- break
- }
- return S
- }
-
- function ProcMessages(Deliver,Kill,Resubmit,Substitution,Messages,RetAddrs,
- Recips,BounceMessage,
- S,Msg,File,List,Items,RecipList,Pattern,i,RepText,Elem,num,Cmd) {
- if (Deliver)
- while ((S = MakeMaxStr(" ",Messages,2000)) != "")
- system("exec /usr/mmdf/bin/deliver -w " S)
- else if (Kill)
- while ((S = MakeMaxStr(" */",Messages,200)) != "") {
- system(GoToHome "exec rm */" S)
- gsub(" ./"," ",S)
- ErrMsg("Killed: " S)
- }
- else if (Resubmit || BounceMessage != "") {
- # Requeue message (using execmail) for all addresses not yet delivered
- # to.
- for (Msg in Messages) {
- if (!(Msg in RetAddrs)) {
- ErrMsg("No address file for message " Msg)
- delete Messages[Msg]
- }
- File = MMDFHome "/msg/" Msg
- if ((getline < File) != 1) {
- ErrMsg("No message file for message " Msg)
- delete Messages[Msg]
- }
- close(File)
- }
-
- # Move msg files first so they won't be delivered while being
- # reinjected
- CopySet(Messages,Items)
- while ((S = MakeMaxStr(" ",Items,2000)) != "")
- system("cd " MMDFHome "/msg; exec mv " S " ../tmp")
-
- # If something is to be substituted for...
- if (Substitution ~ ".+;") {
- i = index(Substitution,";")
- Pattern = substr(Substitution,1,i-1)
- RepText = substr(Substitution,i+1)
- }
- for (Msg in Messages) {
- RecipList = Recips[Msg]
- if (Pattern != "") {
- num = split(RecipList,Elem," ")
- oRecipList = RecipList
- RecipList = ""
- for (i = 1; i <= num; i++) {
- sub(Pattern,RepText,Elem[i])
- RecipList = RecipList " " Elem[i]
- }
- if (Debug)
- printf \
- "Substitution on recipient list finished.\nOld: %s\nNew: %s\n",
- oRecipList,RecipList > "/dev/stderr"
- }
- if (BounceMessage != "") {
- Cmd = "exec /usr/lib/mail/execmail -dv -f /dev/null"
- }
- else
- system( \
- sprintf("exec /usr/lib/mail/execmail -dv -f %s %s < %s/tmp/%s",
- RetAddrs[Msg],RecipList,MMDFHome,Msg))
- }
- while ((S = MakeMaxStr(" */",Messages,200)) != "")
- system(GoToHome "exec rm */" S)
- }
- }
-
- # Generate format string & print headers
- function MakeFormat(Long,OneLine,Verbose,Header,printAge,
- MLen,RLen,QueueHdr,Format) {
- # MLen: Length of Message-ID field.
- # RLen: Length of Return-Addr field.
- MLen = Long ? 11 : 7
- QueueHdr = (Verbose ? "Chan" : "Channel") (OneLine ? "s" : "")
- Format = Verbose ? "%-5s %-" MLen "s %6s %-17s %-4s " : "%-8s %-" MLen "s "
- if (COLUMNS) {
- RLen = int((COLUMNS - length(sprintf(Format,"","","","","")))/2)
- if (RLen < 0)
- RLen = 1
- }
- else
- RLen = 20
- if (Debug)
- printf "Width of return-address field is %d\n",RLen
- Format = Format "%-" RLen "s %s"
- if (Header)
- if (Verbose)
- printf Format "\n",QueueHdr,"Message","Size",
- printAge ? "Age" : "Date&Time Queued",
- "Flgs","Return-addr","Addressees"
- else
- printf Format "\n",QueueHdr,"Message","Return-addr","Addressees"
- return Format
- }
-
- # Check consistency, and set Addr[file] to the name of one of the queue
- # directories that the file can be found in
- function FindAddrFiles(Addr,Q,LimChan, File,QueueDirs) {
- for (File in Q)
- if (!(File in Addr))
- ErrMsg(\
- File " has no link in addr but has links in queues: " Q[File])
- if (LimChan)
- # If processing specific channels, we only needed addr/* files
- # for a consistency check, so empty array now
- split("",Addr,"")
- else
- for (File in Addr) {
- if (!(File in Q))
- ErrMsg("addr/" File " has no link in any q.* directory.")
- Addr[File] = "addr"
- }
- # For any file that has no link in Addr, use the first link found in a
- # queue directory instead.
- for (File in Q)
- if (!(File in Addr)) {
- split(Q[File],QueueDirs)
- Addr[File] = QueueDirs[1]
- }
- }
-
- # Print a line describing a queued message
- # Verbose: true if size, date, and flags should be printed
- # Format: printf format string; varies depending on Verbose
- # Queue: which channel's info for this message is being printed
- # PrintFile: message-ID
- # Size: size of message contents
- # Date: time that message was queued
- # ReturnAddr: return address
- # AddrLine: recipients that would be delivered to via Queue
- # Info: Extra out-of-band info (flags, etc.) for message
- function PrintLine(Verbose,Format,Queue,PrintFile,Size,Date,
- ReturnAddr,AddrLine,Info, Flags) {
- if (Verbose) {
- if (Queue == "!ADDR")
- Flags = "-"
- else {
- if ("late" in Info)
- Flags = "L"
- if ("c" in Info)
- Flags = Flags "C"
- if (!("q" in Info))
- Flags = Flags "R"
- if (!("z" in Info))
- Flags = Flags "W"
- }
- HeadPrint(sprintf(Format,Queue,PrintFile,Size,Date,Flags,ReturnAddr,
- AddrLine))
- }
- else
- HeadPrint(sprintf(Format,Queue,PrintFile,ReturnAddr,AddrLine))
- }
-
- function ErrMsg(S) {
- print S | "cat 1>&2"
- }
-
- # 91/03/13 john h. dubois iii
- # Sets global var "machine" to uucp name,
- # Sets global "domname" to MMDF name for system,
- # and "locname" to name without hostname hidden if MLOCMACHINE is defined.
- # Returns null on success, error message on failure.
- function GetMailName( mlname,mldomain,mlocmachine,proc,tailor) {
- tailor = "/usr/mmdf/mmdftailor"
- if ((getline < tailor) == -1)
- return "Could not open " tailor "."
- else do {
- if ($1 == "MLNAME")
- mlname = $2
- else if ($1 == "MLDOMAIN")
- mldomain = $2
- else if ($1 == "MLOCMACHINE")
- mlocmachine = $2
- else
- continue
- if (mlname != "" && mldomain != "" && mlocmachine != "")
- break
- } while ((getline < tailor) == 1)
- close(tailor)
- if (mlname == "" || mldomain == "")
- return "Could not get MLNAME or MLDOMAIN"
- domname = mlname "." mldomain
- if (mlocmachine != "")
- locname = mlocmachine "." domname
- return ""
- }
-
- # Returns the part of Addr that comes after the last @ character in Addr,
- # if any. If there is no @ character in Addr, returns a null string.
- function GetHostPart(Addr) {
- if (!match(Addr,"@[^@]*$"))
- return ""
- return substr(Addr,RSTART+1)
- }
-
- # Convert Addr to short canonical form: remove host part if it is the same
- # as the local or external mail name; remove source routes.
- function CanonShort(Addr) {
- if (domname != "" && GetHostPart(Addr) == domname)
- Addr = substr(Addr,1,length(Addr) - (length(domname)+1))
- else if (locname != "" && GetHostPart(Addr) == locname)
- Addr = substr(Addr,1,length(Addr) - (length(locname)+1))
- sub("^@.*:","",Addr)
- return Addr
- }
-
- ### Begin set library
-
- function Intersection(A,B,Inter, Elem,Count) {
- for (Elem in A)
- if (Elem in B) {
- Inter[Elem]
- Count++
- }
- return Count
- }
-
- function Union(A,B,Both, Elem) {
- for (Elem in A)
- Both[Elem]
- for (Elem in B)
- Both[Elem]
- }
-
- # Deletes any elements that are in both Minuend and Subtrahend from Minuend.
- function SubtractSet(Minuend,Subtrahend, Elem) {
- for (Elem in Subtrahend)
- delete Minuend[Elem]
- }
-
- function CopySet(From,To, Elem) {
- for (Elem in From)
- To[Elem]
- }
-
- # Returns 1 if Set is empty, 0 if not.
- function IsEmpty(Set, i) {
- for (i in Set)
- return 0
- return 1
- }
-
- # MakeSet: make a set from a list.
- # An index with the name of each element of the list
- # is created in the given array.
- # Input variables:
- # Elements is a string containing the list of elements.
- # Sep is the character that separates the elements of the list.
- # Output variables:
- # Set is the array.
- # Return value: the number of elements added to the set.
- function MakeSet(Set,Elements,Sep, i,Num,Names) {
- Num = split(Elements,Names,Sep)
- for (i = 1; i <= Num; i++)
- Set[Names[i]]
- return Num
- }
- # Returns the number of elements in set Set
- function NumElem(Set, elem,Num) {
- for (elem in Set)
- Num++
- return Num
- }
-
- # Remove all elements from Set
- function DeleteAll(Set, i) {
- for (i in Set)
- delete Set[i]
- }
-
- ### End set library
- ### Begin head-tail routines
-
- # @(#) HeadTail.awk 96/05/09
- # 95/04/28 Added tail routines.
- # 96/05/09 Added all args to HeadTailInit()
-
- # Turn on screen-bounded printing.
- # Current implementation sets global vars LINES, COLUMNS, LINEGAP, and COLGAP.
- # Sets the number of screen lines and rows to Lines and Rows.
- # If -1 is passed for either, turns off bounding in that dimension.
- # If either is not set or 0 is passed for it, its value is taken from the
- # environment, or if not set there, from terminfo, or if not set there, from
- # the defaults (24 and 80).
- # By default, the other functions in this library leave a "grace space" of
- # 1 column and 1 line. If LineGap or ColGap is passed and is a non-negative
- # value, the line gap is set to it.
- function HeadTailInit(Lines,Cols,LineGap,ColGap, Cmd) {
- # tput will use values in environment, but we want to avoid running
- # it if possible.
- if (Cols > 0)
- COLUMNS = Cols
- else if (!Cols)
- if ("COLUMNS" in ENVIRON)
- COLUMNS = ENVIRON["COLUMNS"]
- else {
- Cmd = "exec tput cols"
- Cmd | getline COLUMNS
- close(Cmd)
- if (COLUMNS == "")
- COLUMNS = 80
- }
- if (Lines > 0)
- LINES = Lines
- else if (!Lines)
- if ("LINES" in ENVIRON)
- LINES = ENVIRON["LINES"]
- else {
- Cmd = "exec tput lines"
- Cmd | getline LINES
- close(Cmd)
- if (LINES == "")
- LINES = 24
- }
- LINEGAP = (LineGap != "" && LineGap >= 0) ? LineGap : 1
- COLGAP = (ColGap != "" && ColGap >= 0) ? ColGap : 1
- }
-
- # Do screen-bound printing.
- # If LINES is >0, the last LINES-LINEGAP lines are kept in a circular buffer.
- # When TailFlush() is called, they are printed.
- # If LINES = 0, all lines are printed immediately.
- # If COLUMNS is >0, truncates Line to COLUMNS-COLGAP characters before printing
- # it.
- # Global vars: uses LINES & COLUMNS; sets/uses TailPtr;
- # saves lines in TailLines[] from 1..LINES-LINEGAP
- # Embedded newlines split the line into multiple lines; trailing newlines are
- # stripped. Tabs are expanded to spaces.
- function TailPrint(Line) {
- if (!LINES)
- print Line
- else {
- if (++TailPtr > (LINES-LINEGAP))
- TailPtr = 1
- TailLines[TailPtr] = Line
- }
- }
-
- function TailFlush( NumPrinted,Lines,Line,i,Buffer,PrintLines) {
- if (!LINES)
- return
- NumPrinted = 0
- PrintLines = LINES-LINEGAP
- # Since lines may contain multiple lines, we must create a buffer to be
- # printed by reading line buffer backwards.
- # Stop when we have copied enough lines, or if we wrap around to the end
- # and find that the entire line buffer was not used.
- while (NumPrinted < PrintLines && TailPtr in TailLines) {
- # Split line into individual lines, then process them last to first
- Num = split(TailLines[TailPtr],Lines,"\n")
- for (i = Num; i >= 1; i--) {
- Line = Lines[i]
- if (i == Num && Line == "") # discard trailing newline
- continue
- # Put this line at the front of the print buffer
- if (COLUMNS)
- Buffer = substr(TabEx(Line),1,COLUMNS - COLGAP) "\n" Buffer
- else
- Buffer = Line "\n" Buffer
- if (++NumPrinted == PrintLines)
- break
- }
- if (!--TailPtr) # Wrap pointer if neccessary
- TailPtr = PrintLines
- }
- printf "%s",Buffer
- }
-
- # Do screen-bound printing.
- # If LINES >0, returns 0 when LINES-LINEGAP lines have been printed by
- # HeadPrint(). Otherwise returns 1.
- # If COLUMNS is >0, truncates Line to COLUMNS-COLGAP characters before printing
- # it.
- # Global vars: uses LINES, COLUMNS, LINEGAP, COLGAP; sets/uses LinesPrinted.
- # Line should not include newlines.
- function HeadPrint(Line) {
- # Check first, in case some calls of this function to not check return
- # value, and in case LINES is 1.
- if (LINES && LinesPrinted >= (LINES-LINEGAP))
- return 0
- if (COLUMNS)
- print substr(Line,1,COLUMNS - COLGAP)
- else
- print Line
- if (LINES && ++LinesPrinted >= (LINES-LINEGAP))
- return 0
- return 1
- }
-
- function ColPrint(Line) {
- if (COLUMNS)
- print substr(Line,1,COLUMNS - COLGAP)
- else
- print Line
- return 1
- }
-
- ### End head-tail routines
- ### Begin copy,append,cat routines
- # append: append file Source to file Dest.
- # The final return value from the read is returned.
- # It will be 0 if the file was read successfully; -1 if not.
- function append(Source,Dest, Line,ret) {
- while ((ret = (getline Line < Source)) == 1)
- print Line >> Dest
- close(Source)
- close(Dest)
- return ret
- }
-
- # copy: append file Source to file Dest.
- # The final return value from the read is returned.
- # It will be 0 if the file was read successfully; -1 if not.
- function copy(Source,Dest, Line,ret) {
- while ((ret = (getline Line < Source)) == 1)
- print Line > Dest
- close(Source)
- close(Dest)
- return ret
- }
-
- # cat: Print a file.
- # The final return value from the read is returned.
- # It will be 0 if the file was read successfully; -1 if not.
- function cat(Source, Line,ret) {
- while ((ret = (getline Line < Source)) == 1)
- print Line
- close(Source)
- return ret
- }
- ### End copy,append,cat routines
- ### Begin UnControl routines
-
- # @(#) uncontrol.awk 1.1 96/05/29
- # 92/11/09 john h. dubois iii (john@armory.com)
- # 96/05/29 Added octal-only conversion.
-
- # Uncontrol(S): Convert control characters in S to symbolic form.
- # Characters in S with values < 32 and with value 127 are converted to the form
- # ^X. Characters with value >= 128 are converted to the octal form \0nnn,
- # where nnn is the octal value of the character.
- # The resulting string is returned.
- # If OctalOnly is true, octal numbers are used for all symbolic values instead
- # of ^X.
- # Global variables: UncTable[] and char2octal[].
- function Uncontrol(S,OctalOnly, i,len,Output) {
- len = length(S)
- Output = ""
- if (!("a" in UncTable))
- MakeUncontrolTable()
- for (i = 1; i <= len; i++)
- Output = Output \
- (OctalOnly ? char2octal[substr(S,i,1)] : UncTable[substr(S,i,1)])
- return Output
- }
-
- # MakeUncontrolTable: Make tables for use by Uncontrol().
- # Global variables:
- # UncTable[] is made into a character -> symbolic character lookup table
- # with characters with values < 32 and with value 127 converted to the form
- # ^X, and characters with value >= 128 are converted to the octal form \0nnn.
- # char2octal[] is made into a similar table but with all non-printing chars
- # in the form \0nnn.
- function MakeUncontrolTable( i,c) {
- for (i = 0; i < 32; i++) {
- UncTable[c = sprintf("%c",i)] = "^" sprintf("%c",i + 64)
- char2octal[c] = "\\" sprintf("%03o",i)
- }
- for (i = 32; i < 127; i++) {
- c = sprintf("%c",i)
- char2octal[c] = UncTable[c] = sprintf("%c",i)
- }
- UncTable[c = sprintf("%c",127)] = "^?"
- char2octal[c] = "\\0177"
- for (i = 128; i < 256; i++) {
- UncTable[c = sprintf("%c",i)] = "\\" sprintf("%03o",i)
- char2octal[c] = "\\" sprintf("%03o",i)
- }
- }
-
- ### End UnControl routines
- ### Start of ProcArgs library
- # @(#) ProcArgs 1.11 96/12/08
- # 92/02/29 john h. dubois iii (john@armory.com)
- # 93/07/18 Added "#" arg type
- # 93/09/26 Do not count -h against MinArgs
- # 94/01/01 Stop scanning at first non-option arg. Added ">" option type.
- # Removed meaning of "+" or "-" by itself.
- # 94/03/08 Added & option and *()< option types.
- # 94/04/02 Added NoRCopt to Opts()
- # 94/06/11 Mark numeric variables as such.
- # 94/07/08 Opts(): Do not require any args if h option is given.
- # 95/01/22 Record options given more than once. Record option num in argv.
- # 95/06/08 Added ExclusiveOptions().
- # 96/01/20 Let rcfiles be a colon-separated list of filenames.
- # Expand $VARNAME at the start of its filenames.
- # Let varname=0 and -option- turn off an option.
- # 96/05/05 Changed meaning of 7th arg to Opts; now can specify exactly how many
- # of the vars should be searched for in the environment.
- # Check for duplicate rcfiles.
- # 96/05/13 Return more specific error values. Note: ProcArgs() and InitOpts()
- # now return various negatives values on error, not just -1, and
- # Opts() may set Err to various positive values, not just 1.
- # Added AllowUnrecOpt.
- # 96/05/23 Check type given for & option
- # 96/06/15 Re-port to awk
- # 96/10/01 Moved file-reading code into ReadConfFile(), so that it can be
- # used by other functions.
- # 96/10/15 Added OptChars
- # 96/11/01 Added exOpts arg to Opts()
- # 96/11/16 Added ; type
- # 96/12/08 Added Opt2Set() & Opt2Sets()
- # 96/12/27 Added CmdLineOpt()
-
- # optlist is a string which contains all of the possible command line options.
- # A character followed by certain characters indicates that the option takes
- # an argument, with type as follows:
- # : String argument
- # ; Non-empty string argument
- # * Floating point argument
- # ( Non-negative floating point argument
- # ) Positive floating point argument
- # # Integer argument
- # < Non-negative integer argument
- # > Positive integer argument
- # The only difference the type of argument makes is in the runtime argument
- # error checking that is done.
-
- # The & option is a special case used to get numeric options without the
- # user having to give an option character. It is shorthand for [-+.0-9].
- # If & is included in optlist and an option string that begins with one of
- # these characters is seen, the value given to "&" will include the first
- # char of the option. & must be followed by a type character other than ":"
- # or ";".
- # Note that if e.g. &> is given, an option of -.5 will produce an error.
-
- # Strings in argv[] which begin with "-" or "+" are taken to be
- # strings of options, except that a string which consists solely of "-"
- # or "+" is taken to be a non-option string; like other non-option strings,
- # it stops the scanning of argv and is left in argv[].
- # An argument of "--" or "++" also stops the scanning of argv[] but is removed.
- # If an option takes an argument, the argument may either immediately
- # follow it or be given separately.
- # "-" and "+" options are treated the same. "+" is allowed because most awks
- # take any -options to be arguments to themselves. gawk 2.15 was enhanced to
- # stop scanning when it encounters an unrecognized option, though until 2.15.5
- # this feature had a flaw that caused problems in some cases. See the OptChars
- # parameter to explicitly set the option-specifier characters.
-
- # If an option that does not take an argument is given,
- # an index with its name is created in Options and its value is set to the
- # number of times it occurs in argv[].
-
- # If an option that does take an argument is given, an index with its name is
- # created in Options and its value is set to the value of the argument given
- # for it, and Options[option-name,"count"] is (initially) set to the 1.
- # If an option that takes an argument is given more than once,
- # Options[option-name,"count"] is incremented, and the value is assigned to
- # the index (option-name,instance) where instance is 2 for the second occurance
- # of the option, etc.
- # In other words, the first time an option with a value is encountered, the
- # value is assigned to an index consisting only of its name; for any further
- # occurances of the option, the value index has an extra (count) dimension.
-
- # The sequence number for each option found in argv[] is stored in
- # Options[option-name,"num",instance], where instance is 1 for the first
- # occurance of the option, etc. The sequence number starts at 1 and is
- # incremented for each option, both those that have a value and those that
- # do not. Options set from a config file have a value of 0 assigned to this.
-
- # Options and their arguments are deleted from argv.
- # Note that this means that there may be gaps left in the indices of argv[].
- # If compress is nonzero, argv[] is packed by moving its elements so that
- # they have contiguous integer indices starting with 0.
- # Option processing will stop with the first unrecognized option, just as
- # though -- was given except that unlike -- the unrecognized option will not be
- # removed from ARGV[]. Normally, an error value is returned in this case.
- # If AllowUnrecOpt is true, it is not an error for an unrecognized option to
- # be found, so the number of remaining arguments is returned instead.
- # If OptChars is not a null string, it is the set of characters that indicate
- # that an argument is an option string if the string begins with one of the
- # characters. A string consisting solely of two of the same option-indicator
- # characters stops the scanning of argv[]. The default is "-+".
- # argv[0] is not examined.
- # The number of arguments left in argc is returned.
- # If an error occurs, the global string OptErr is set to an error message
- # and a negative value is returned.
- # Current error values:
- # -1: option that required an argument did not get it.
- # -2: argument of incorrect type supplied for an option.
- # -3: unrecognized (invalid) option.
- function ProcArgs(argc,argv,OptList,Options,compress,AllowUnrecOpt,OptChars,
- ArgNum,ArgsLeft,Arg,ArgLen,ArgInd,Option,Pos,NumOpt,Value,HadValue,specGiven,
- NeedNextOpt,GotValue,OptionNum,Escape,dest,src,count,c,OptTerm,OptCharSet)
- {
- # ArgNum is the index of the argument being processed.
- # ArgsLeft is the number of arguments left in argv.
- # Arg is the argument being processed.
- # ArgLen is the length of the argument being processed.
- # ArgInd is the position of the character in Arg being processed.
- # Option is the character in Arg being processed.
- # Pos is the position in OptList of the option being processed.
- # NumOpt is true if a numeric option may be given.
- ArgsLeft = argc
- NumOpt = index(OptList,"&")
- OptionNum = 0
- if (OptChars == "")
- OptChars = "-+"
- while (OptChars != "") {
- c = substr(OptChars,1,1)
- OptChars = substr(OptChars,2)
- OptCharSet[c]
- OptTerm[c c]
- }
- for (ArgNum = 1; ArgNum < argc; ArgNum++) {
- Arg = argv[ArgNum]
- if (length(Arg) < 2 || !((specGiven = substr(Arg,1,1)) in OptCharSet))
- break # Not an option; quit
- if (Arg in OptTerm) {
- delete argv[ArgNum]
- ArgsLeft--
- break
- }
- ArgLen = length(Arg)
- for (ArgInd = 2; ArgInd <= ArgLen; ArgInd++) {
- Option = substr(Arg,ArgInd,1)
- if (NumOpt && Option ~ /[-+.0-9]/) {
- # If this option is a numeric option, make its flag be & and
- # its option string flag position be the position of & in
- # the option string.
- Option = "&"
- Pos = NumOpt
- # Prefix Arg with a char so that ArgInd will point to the
- # first char of the numeric option.
- Arg = "&" Arg
- ArgLen++
- }
- # Find position of flag in option string, to get its type (if any).
- # Disallow & as literal flag.
- else if (!(Pos = index(OptList,Option)) || Option == "&") {
- if (AllowUnrecOpt) {
- Escape = 1
- break
- }
- else {
- OptErr = "Invalid option: " specGiven Option
- return -3
- }
- }
-
- # Find what the value of the option will be if it takes one.
- # NeedNextOpt is true if the option specifier is the last char of
- # this arg, which means that if the option requires a value it is
- # the next arg.
- if (NeedNextOpt = (ArgInd >= ArgLen)) { # Value is the next arg
- if (GotValue = ArgNum + 1 < argc)
- Value = argv[ArgNum+1]
- }
- else { # Value is included with option
- Value = substr(Arg,ArgInd + 1)
- GotValue = 1
- }
-
- if (HadValue = AssignVal(Option,Value,Options,
- substr(OptList,Pos + 1,1),GotValue,"",++OptionNum,!NeedNextOpt,
- specGiven)) {
- if (HadValue < 0) # error occured
- return HadValue
- if (HadValue == 2)
- ArgInd++ # Account for the single-char value we used.
- else {
- if (NeedNextOpt) { # option took next arg as value
- delete argv[++ArgNum]
- ArgsLeft--
- }
- break # This option has been used up
- }
- }
- }
- if (Escape)
- break
- # Do not delete arg until after processing of it, so that if it is not
- # recognized it can be left in ARGV[].
- delete argv[ArgNum]
- ArgsLeft--
- }
- if (compress != 0) {
- dest = 1
- src = argc - ArgsLeft + 1
- for (count = ArgsLeft - 1; count; count--) {
- ARGV[dest] = ARGV[src]
- dest++
- src++
- }
- }
- return ArgsLeft
- }
-
- # Assignment to values in Options[] occurs only in this function.
- # Option: Option specifier character.
- # Value: Value to be assigned to option, if it takes a value.
- # Options[]: Options array to return values in.
- # ArgType: Argument type specifier character.
- # GotValue: Whether any value is available to be assigned to this option.
- # Name: Name of option being processed.
- # OptionNum: Number of this option (starting with 1) if set in argv[],
- # or 0 if it was given in a config file or in the environment.
- # SingleOpt: true if the value (if any) that is available for this option was
- # given as part of the same command line arg as the option. Used only for
- # options from the command line.
- # specGiven is the option specifier character use, if any (e.g. - or +),
- # for use in error messages.
- # Global variables: OptErr
- # Return value: negative value on error, 0 if option did not require an
- # argument, 1 if it did & used the whole arg, 2 if it required just one char of
- # the arg.
- # Current error values:
- # -1: Option that required an argument did not get it.
- # -2: Value of incorrect type supplied for option.
- # -3: Bad type given for option &
- function AssignVal(Option,Value,Options,ArgType,GotValue,Name,OptionNum,
- SingleOpt,specGiven, UsedValue,Err,NumTypes) {
- # If option takes a value... [
- NumTypes = "*()#<>]"
- if (Option == "&" && ArgType !~ "[" NumTypes) { # ]
- OptErr = "Bad type given for & option"
- return -3
- }
-
- if (UsedValue = (ArgType ~ "[:;" NumTypes)) { # ]
- if (!GotValue) {
- if (Name != "")
- OptErr = "Variable requires a value -- " Name
- else
- OptErr = "option requires an argument -- " Option
- return -1
- }
- if ((Err = CheckType(ArgType,Value,Option,Name,specGiven)) != "") {
- OptErr = Err
- return -2
- }
- # Mark this as a numeric variable; will be propogated to Options[] val.
- if (ArgType != ":" && ArgType != ";")
- Value += 0
- if ((Instance = ++Options[Option,"count"]) > 1)
- Options[Option,Instance] = Value
- else
- Options[Option] = Value
- }
- # If this is an environ or rcfile assignment & it was given a value...
- else if (!OptionNum && Value != "") {
- UsedValue = 1
- # If the value is "0" or "-" and this is the first instance of it,
- # do not set Options[Option]; this allows an assignment in an rcfile to
- # turn off an option (for the simple "Option in Options" test) in such
- # a way that it cannot be turned on in a later file.
- if (!(Option in Options) && (Value == "0" || Value == "-"))
- Instance = 1
- else
- Instance = ++Options[Option]
- # Save the value even though this is a flag
- Options[Option,Instance] = Value
- }
- # If this is a command line flag and has a - following it in the same arg,
- # it is being turned off.
- else if (OptionNum && SingleOpt && substr(Value,1,1) == "-") {
- UsedValue = 2
- if (Option in Options)
- Instance = ++Options[Option]
- else
- Instance = 1
- Options[Option,Instance]
- }
- # If this is a flag assignment without a value, increment the count for the
- # flag unless it was turned off. The indicator for a flag being turned off
- # is that the flag index has not been set in Options[] but it has an
- # instance count.
- else if (Option in Options || !((Option,1) in Options))
- # Increment number of times this flag seen; will inc null value to 1
- Instance = ++Options[Option]
- Options[Option,"num",Instance] = OptionNum
- return UsedValue
- }
-
- # Option is the option letter
- # Value is the value being assigned
- # Name is the var name of the option, if any
- # ArgType is one of:
- # : String argument
- # ; Non-null string argument
- # * Floating point argument
- # ( Non-negative floating point argument
- # ) Positive floating point argument
- # # Integer argument
- # < Non-negative integer argument
- # > Positive integer argument
- # specGiven is the option specifier character use, if any (e.g. - or +),
- # for use in error messages.
- # Returns null on success, err string on error
- function CheckType(ArgType,Value,Option,Name,specGiven, Err,ErrStr) {
- if (ArgType == ":")
- return ""
- if (ArgType == ";") {
- if (Value == "")
- Err = "must be a non-empty string"
- }
- # A number begins with optional + or -, and is followed by a string of
- # digits or a decimal with digits before it, after it, or both
- else if (Value !~ /^[-+]?([0-9]+|[0-9]*\.[0-9]+|[0-9]+\.)$/)
- Err = "must be a number"
- else if (ArgType ~ "[#<>]" && Value ~ /\./)
- Err = "may not include a fraction"
- else if (ArgType ~ "[()<>]" && Value < 0)
- Err = "may not be negative"
- # (
- else if (ArgType ~ "[)>]" && Value == 0)
- Err = "must be a positive number"
- if (Err != "") {
- ErrStr = "Bad value \"" Value "\". Value assigned to "
- if (Name != "")
- return ErrStr "variable " substr(Name,1,1) " " Err
- else {
- if (Option == "&")
- Option = Value
- return ErrStr "option " specGiven substr(Option,1,1) " " Err
- }
- }
- else
- return ""
- }
-
- # Note: only the above functions are needed by ProcArgs.
- # The rest of these functions call ProcArgs() and also do other
- # option-processing stuff.
-
- # Opts: Process command line arguments.
- # Opts processes command line arguments using ProcArgs()
- # and checks for errors. If an error occurs, a message is printed
- # and the program is exited.
- #
- # Input variables:
- # Name is the name of the program, for error messages.
- # Usage is a usage message, for error messages.
- # OptList the option description string, as used by ProcArgs().
- # MinArgs is the minimum number of non-option arguments that this
- # program should have, non including ARGV[0] and +h.
- # If the program does not require any non-option arguments,
- # MinArgs should be omitted or given as 0.
- # rcFiles, if given, is a colon-seprated list of filenames to read for
- # variable initialization. If a filename begins with ~/, the ~ is replaced
- # by the value of the environment variable HOME. If a filename begins with
- # $, the part from the character after the $ up until (but not including)
- # the first character not in [a-zA-Z0-9_] will be searched for in the
- # environment; if found its value will be substituted, if not the filename will
- # be discarded.
- # rcfiles are read in the order given.
- # Values given in them will not override values given on the command line,
- # and values given in later files will not override those set in earlier
- # files, because AssignVal() will store each with a different instance index.
- # The first instance of each variable, either on the command line or in an
- # rcfile, will be stored with no instance index, and this is the value
- # normally used by programs that call this function.
- # VarNames is a comma-separated list of variable names to map to options,
- # in the same order as the options are given in OptList.
- # If EnvSearch is given and nonzero, the first EnvSearch variables will also be
- # searched for in the environment. If set to -1, all values will be searched
- # for in the environment. Values given in the environment will override
- # those given in the rcfiles but not those given on the command line.
- # NoRCopt, if given, is an additional letter option that if given on the
- # command line prevents the rcfiles from being read.
- # See ProcArgs() for a description of AllowUnRecOpt and optChars, and
- # ExclusiveOptions() for a description of exOpts.
- # Special options:
- # If x is made an option and is given, some debugging info is output.
- # h is assumed to be the help option.
-
- # Global variables:
- # The command line arguments are taken from ARGV[].
- # The arguments that are option specifiers and values are removed from
- # ARGV[], leaving only ARGV[0] and the non-option arguments.
- # The number of elements in ARGV[] should be in ARGC.
- # After processing, ARGC is set to the number of elements left in ARGV[].
- # The option values are put in Options[].
- # On error, Err is set to a positive integer value so it can be checked for in
- # an END block.
- # Return value: The number of elements left in ARGV is returned.
- # Must keep OptErr global since it may be set by InitOpts().
- function Opts(Name,Usage,OptList,MinArgs,rcFiles,VarNames,EnvSearch,NoRCopt,
- AllowUnrecOpt,optChars,exOpts, ArgsLeft,e) {
- if (MinArgs == "")
- MinArgs = 0
- ArgsLeft = ProcArgs(ARGC,ARGV,OptList NoRCopt,Options,1,AllowUnrecOpt,
- optChars)
- if (ArgsLeft < (MinArgs+1) && !("h" in Options)) {
- if (ArgsLeft >= 0) {
- OptErr = "Not enough arguments"
- Err = 4
- }
- else
- Err = -ArgsLeft
- printf "%s: %s.\nUse -h for help.\n%s\n",
- Name,OptErr,Usage > "/dev/stderr"
- exit 1
- }
- if (rcFiles != "" && (NoRCopt == "" || !(NoRCopt in Options)) &&
- (e = InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch)) < 0)
- {
- print Name ": " OptErr ".\nUse -h for help." > "/dev/stderr"
- Err = -e
- exit 1
- }
- if ((exOpts != "") && ((OptErr = ExclusiveOptions(exOpts,Options)) != ""))
- {
- printf "%s: Error: %s\n",Name,OptErr > "/dev/stderr"
- Err = 1
- exit 1
- }
- return ArgsLeft
- }
-
- # ReadConfFile(): Read a file containing var/value assignments, in the form
- # <variable-name><assignment-char><value>.
- # Whitespace (spaces and tabs) around a variable (leading whitespace on the
- # line and whitespace between the variable name and the assignment character)
- # is stripped. Lines that do not contain an assignment operator or which
- # contain a null variable name are ignored, other than possibly being noted in
- # the return value. If more than one assignment is made to a variable, the
- # first assignment is used.
- # Input variables:
- # File is the file to read.
- # Comment is the line-comment character. If it is found as the first non-
- # whitespace character on a line, the line is ignored.
- # Assign is the assignment string. The first instance of Assign on a line
- # separates the variable name from its value.
- # If StripWhite is true, whitespace around the value (whitespace between the
- # assignment char and trailing whitespace on the line) is stripped.
- # VarPat is a pattern that variable names must match.
- # Example: "^[a-zA-Z][a-zA-Z0-9]+$"
- # If FlagsOK is true, variables are allowed to be "set" by being put alone on
- # a line; no assignment operator is needed. These variables are set in
- # the output array with a null value. Lines containing nothing but
- # whitespace are still ignored.
- # Output variables:
- # Values[] contains the assignments, with the indexes being the variable names
- # and the values being the assigned values.
- # Lines[] contains the line number that each variable occured on. A flag set
- # is record by giving it an index in Lines[] but not in Values[].
- # Return value:
- # If any errors occur, a string consisting of descriptions of the errors
- # separated by newlines is returned. In no case will the string start with a
- # numeric value. If no errors occur, the number of lines read is returned.
- function ReadConfigFile(Values,Lines,File,Comment,Assign,StripWhite,VarPat,
- FlagsOK,
- Line,Status,Errs,AssignLen,LineNum,Var,Val) {
- if (Comment != "")
- Comment = "^" Comment
- AssignLen = length(Assign)
- if (VarPat == "")
- VarPat = "." # null varname not allowed
- while ((Status = (getline Line < File)) == 1) {
- LineNum++
- sub("^[ \t]+","",Line)
- if (Line == "") # blank line
- continue
- if (Comment != "" && Line ~ Comment)
- continue
- if (Pos = index(Line,Assign)) {
- Var = substr(Line,1,Pos-1)
- Val = substr(Line,Pos+AssignLen)
- if (StripWhite) {
- sub("^[ \t]+","",Val)
- sub("[ \t]+$","",Val)
- }
- }
- else {
- Var = Line # If no value, var is entire line
- Val = ""
- }
- if (!FlagsOK && Val == "") {
- Errs = Errs \
- sprintf("\nBad assignment on line %d of file %s: %s",
- LineNum,File,Line)
- continue
- }
- sub("[ \t]+$","",Var)
- if (Var !~ VarPat) {
- Errs = Errs sprintf("\nBad variable name on line %d of file %s: %s",
- LineNum,File,Var)
- continue
- }
- if (!(Var in Lines)) {
- Lines[Var] = LineNum
- if (Pos)
- Values[Var] = Val
- }
- }
- if (Status)
- Errs = Errs "\nCould not read file " File
- close(File)
- return Errs == "" ? LineNum : substr(Errs,2) # Skip first newline
- }
-
- # Variables:
- # Data is stored in Options[].
- # rcFiles, OptList, VarNames, and EnvSearch are as as described for Opts().
- # Global vars:
- # Sets OptErr. Uses ENVIRON[].
- # If anything is read from any of the rcfiles, sets READ_RCFILE to 1.
- function InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch,
- Line,Var,Pos,Vars,Map,CharOpt,NumVars,TypesInd,Types,Type,Ret,i,rcFile,
- fNames,numrcFiles,filesRead,Err,Values,retStr) {
- split("",filesRead,"") # make awk know this is an array
- NumVars = split(VarNames,Vars,",")
- TypesInd = Ret = 0
- if (EnvSearch == -1)
- EnvSearch = NumVars
- for (i = 1; i <= NumVars; i++) {
- Var = Vars[i]
- CharOpt = substr(OptList,++TypesInd,1)
- if (CharOpt ~ "^[:;*()#<>&]$")
- CharOpt = substr(OptList,++TypesInd,1)
- Map[Var] = CharOpt
- Types[Var] = Type = substr(OptList,TypesInd+1,1)
- # Do not overwrite entries from environment
- if (i <= EnvSearch && Var in ENVIRON &&
- (Err = AssignVal(CharOpt,ENVIRON[Var],Options,Type,1,Var,0)) < 0)
- return Err
- }
-
- numrcFiles = split(rcFiles,fNames,":")
- for (i = 1; i <= numrcFiles; i++) {
- rcFile = fNames[i]
- if (rcFile ~ "^~/")
- rcFile = ENVIRON["HOME"] substr(rcFile,2)
- else if (rcFile ~ /^\$/) {
- rcFile = substr(rcFile,2)
- match(rcFile,"^[a-zA-Z0-9_]*")
- envvar = substr(rcFile,1,RLENGTH)
- if (envvar in ENVIRON)
- rcFile = ENVIRON[envvar] substr(rcFile,RLENGTH+1)
- else
- continue
- }
- if (rcFile in filesRead)
- continue
- # rcfiles are liable to be given more than once, e.g. UHOME and HOME
- # may be the same
- filesRead[rcFile]
- if ("x" in Options)
- printf "Reading configuration file %s\n",rcFile > "/dev/stderr"
- retStr = ReadConfigFile(Values,Lines,rcFile,"#","=",0,"",1)
- if (retStr > 0)
- READ_RCFILE = 1
- else if (ret != "") {
- OptErr = retStr
- Ret = -1
- }
- for (Var in Lines)
- if (Var in Map) {
- if ((Err = AssignVal(Map[Var],
- Var in Values ? Values[Var] : "",Options,Types[Var],
- Var in Values,Var,0)) < 0)
- return Err
- }
- else {
- OptErr = sprintf(\
- "Unknown var \"%s\" assigned to on line %d\nof file %s",Var,
- Lines[Var],rcFile)
- Ret = -1
- }
- }
-
- if ("x" in Options)
- for (Var in Map)
- if (Map[Var] in Options)
- printf "(%s) %s=%s\n",Map[Var],Var,Options[Map[Var]] > \
- "/dev/stderr"
- else
- printf "(%s) %s not set\n",Map[Var],Var > "/dev/stderr"
- return Ret
- }
-
- # OptSets is a semicolon-separated list of sets of option sets.
- # Within a list of option sets, the option sets are separated by commas. For
- # each set of sets, if any option in one of the sets is in Options[] AND any
- # option in one of the other sets is in Options[], an error string is returned.
- # If no conflicts are found, nothing is returned.
- # Example: if OptSets = "ab,def,g;i,j", an error will be returned due to
- # the exclusions presented by the first set of sets (ab,def,g) if:
- # (a or b is in Options[]) AND (d, e, or f is in Options[]) OR
- # (a or b is in Options[]) AND (g is in Options) OR
- # (d, e, or f is in Options[]) AND (g is in Options)
- # An error will be returned due to the exclusions presented by the second set
- # of sets (i,j) if: (i is in Options[]) AND (j is in Options[]).
- # todo: make options given on command line unset options given in config file
- # todo: that they conflict with.
- function ExclusiveOptions(OptSets,Options,
- Sets,SetSet,NumSets,Pos1,Pos2,Len,s1,s2,c1,c2,ErrStr,L1,L2,SetSets,NumSetSets,
- SetNum,OSetNum) {
- NumSetSets = split(OptSets,SetSets,";")
- # For each set of sets...
- for (SetSet = 1; SetSet <= NumSetSets; SetSet++) {
- # NumSets is the number of sets in this set of sets.
- NumSets = split(SetSets[SetSet],Sets,",")
- # For each set in a set of sets except the last...
- for (SetNum = 1; SetNum < NumSets; SetNum++) {
- s1 = Sets[SetNum]
- L1 = length(s1)
- for (Pos1 = 1; Pos1 <= L1; Pos1++)
- # If any of the options in this set was given, check whether
- # any of the options in the other sets was given. Only check
- # later sets since earlier sets will have already been checked
- # against this set.
- if ((c1 = substr(s1,Pos1,1)) in Options)
- for (OSetNum = SetNum+1; OSetNum <= NumSets; OSetNum++) {
- s2 = Sets[OSetNum]
- L2 = length(s2)
- for (Pos2 = 1; Pos2 <= L2; Pos2++)
- if ((c2 = substr(s2,Pos2,1)) in Options)
- ErrStr = ErrStr "\n"\
- sprintf("Cannot give both %s and %s options.",
- c1,c2)
- }
- }
- }
- if (ErrStr != "")
- return substr(ErrStr,2)
- return ""
- }
-
- # The value of each instance of option Opt that occurs in Options[] is made an
- # index of Set[].
- # The return value is the number of instances of Opt in Options.
- function Opt2Set(Options,Opt,Set, count) {
- if (!(Opt in Options))
- return 0
- Set[Options[Opt]]
- count = Options[Opt,"count"]
- for (; count > 1; count--)
- Set[Options[Opt,count]]
- return count
- }
-
- # The value of each instance of option Opt that occurs in Options[] that
- # begins with "!" is made an index of nSet[] (with the ! stripped from it).
- # Other values are made indexes of Set[].
- # The return value is the number of instances of Opt in Options.
- function Opt2Sets(Options,Opt,Set,nSet, count,aSet,ret) {
- ret = Opt2Set(Options,Opt,aSet)
- for (value in aSet)
- if (substr(value,1,1) == "!")
- nSet[substr(value,2)]
- else
- Set[value]
- return ret
- }
-
- # Returns true if option Opt was given on the command line.
- function CmdLineOpt(Options,Opt, i) {
- for (i = 1; (Opt,"num",i) in Options; i++)
- if (Options[Opt,"num",i] != 0)
- return 1
- return 0
- }
- ### End of ProcArgs library
- ### Begin timeperiod routines.
- # These functions operate on periods of time.
-
- # Converts Seconds to the form [[[[<days>d]<hours>h]<min>m]<sec>s]
- function sec2dhms(Seconds, Days,Hours,Minutes,Time) {
- Days = int(Seconds / 86400)
- Seconds %= 86400
- Hours = int(Seconds / 3600)
- Seconds %= 3600
- Minutes = int(Seconds / 60)
- Seconds %= 60
- if (Days)
- Time = Days "d"
- if (Time || Hours)
- Time = Time Hours "h"
- if (Time || Minutes)
- Time = Time Minutes "m"
- if (!Time || Seconds)
- Time = Time Seconds "s"
- return Time
- }
-
- # Converts Seconds to the form [[[<days>d]hh:]mm:]ss
- function sec2dhms2(Seconds, Days,Hours,Minutes,Time) {
- Days = int(Seconds / 86400)
- Seconds %= 86400
- Hours = int(Seconds / 3600)
- Seconds %= 3600
- Minutes = int(Seconds / 60)
- Seconds %= 60
- if (Days)
- Time = Days "d "
- if (Time || Hours)
- Time = Time sprintf("%02d",Hours) ":"
- if (Time || Minutes)
- Time = Time sprintf("%02d",Minutes) ":"
- Time = Time sprintf("%02d",Seconds)
- return Time
- }
-
- # Converts Seconds to the form [[<days>d]hh:]mm|m
- function sec2dhm(Seconds, Days,Hours,Minutes,Time) {
- Days = int(Seconds / 86400)
- Seconds %= 86400
- Hours = int(Seconds / 3600)
- Seconds %= 3600
- Minutes = int(Seconds / 60)
- if (Days)
- Time = Days "d "
- if (Time || Hours)
- Time = Time sprintf("%02d",Hours) ":"
- if (Time)
- Time = Time sprintf("%02d",Minutes)
- else
- Time = Minutes
- return Time
- }
-
- # Converts Seconds to the form hours:mm:ss
- function sec2hms(Seconds, Hours,Minutes) {
- Hours = int(Seconds / 3600)
- Seconds %= 3600
- Minutes = int(Seconds / 60)
- Seconds %= 60
- return sprintf("%d:%02d:%02d",Hours,Minutes,Seconds)
- }
-
- ### End timeperiod routines
-